﻿package connection {
	import flash.display.MovieClip;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import HandlerEvent.PacketHandler;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	
	public class Connection extends MovieClip{

		public var sock:Socket;
		public var recvPacket:Packet;
		private var tempPacket:Packet;
		private var tempByteArray:ByteArray;
		public var tempByteRemain:int;
		public var byteAvailable:int;
		public var invokeMap:Dictionary;
		
		private static const TIME_OUT = 20;
		private var myTimer:Timer;
		
		public function Connection() {
			// constructor code
			tempPacket = new Packet();
			tempByteArray = new ByteArray();
			recvPacket = new Packet();
			tempByteArray.endian = Endian.LITTLE_ENDIAN;
			sock = new Socket();
			sock.endian = Endian.LITTLE_ENDIAN;
			myTimer = new Timer(TIME_OUT*1000);
			myTimer.addEventListener(TimerEvent.TIMER,doSendPing);
			myTimer.start();
			invokeMap = new Dictionary();
			
		}
		
		public function doSendPing(event:TimerEvent):void{
			var pingPacket:Packet = new Packet();
			pingPacket.setHeaderFlag(0);
			pingPacket.setCommandCode("PNG");
			pingPacket.setCommandType(0);
			pingPacket.setContent(JSON.stringify([]));
			var randomValue:int = Math.floor(Math.random() * 1000000000);
			pingPacket.commandSenderID = randomValue;
			pingPacket.setHeaderDataLength(8+pingPacket.getContent().toString().length);
			sendPacket(pingPacket);
		}
		
		public function connect(h:String,p:int):void{
			sock.connect(h,p);
			doConnect();
		}
		
		public function getSock():Socket{
			return sock;
		}
		
		public function doConnect():void{
			sock.addEventListener(ProgressEvent.SOCKET_DATA,receivePacket);
			sock.addEventListener(IOErrorEvent.IO_ERROR,ioErrorConnection);
			sock.addEventListener(Event.CLOSE,closeConnection);
		}
		
		public function getReceivePacket():Packet{
			return this.recvPacket;
		}
		
		public function timeOutEvent(senderID,context:Context):void{
			context.receiveCallback(context.sentPacket,context.receivedPacket,context.args);
			removeCallback(recvPacket.commandSenderID);
		}
		
		public function removeCallback(senderID):void{
			delete this.invokeMap[recvPacket.getCommandSenderID()];
		}
		
		public function receivePacket(event:ProgressEvent):void{
			byteAvailable = sock.bytesAvailable;
			if(tempByteRemain == 0){
				byteAvailable = sock.bytesAvailable;
				tempPacket.setHeaderFlag(sock.readByte());
				tempPacket.setHeaderDataLength(sock.readInt());
				if(tempPacket.getDataLength() == byteAvailable - 5 ){
					tempPacket.setCommandCode(sock.readUTFBytes(3));
					tempPacket.setCommandType(sock.readByte());
					tempPacket.setCommandSenderID(sock.readInt());
					tempPacket.setContent(sock.readUTFBytes(sock.bytesAvailable));
					recvPacket = tempPacket;
					recvPacket.printPacket();
					tempPacket = new Packet();
					handlerRecivePacket();
					
				}else if(tempPacket.getDataLength() > byteAvailable - 5){
					tempByteRemain = tempPacket.getDataLength() - sock.bytesAvailable;
					sock.readBytes(tempByteArray,tempByteArray.bytesAvailable,sock.bytesAvailable);
				}
			}else if(tempByteRemain - sock.bytesAvailable > 0){
				tempByteRemain -= sock.bytesAvailable;
				sock.readBytes(tempByteArray,tempByteArray.bytesAvailable,sock.bytesAvailable);
			}else if(tempByteRemain - sock.bytesAvailable == 0){
				sock.readBytes(tempByteArray,tempByteArray.bytesAvailable,sock.bytesAvailable);
				tempPacket.setCommandCode(tempByteArray.readUTFBytes(3));
				tempPacket.setCommandType(tempByteArray.readByte());
				tempPacket.setCommandSenderID(tempByteArray.readInt());
				tempPacket.setContent(tempByteArray.readUTFBytes(tempByteArray.bytesAvailable));
				recvPacket = tempPacket;
				recvPacket.printPacket();
				tempPacket = new Packet();
				tempByteArray = new ByteArray()
				tempByteArray.endian = Endian.LITTLE_ENDIAN;
				tempByteRemain = 0;
				handlerRecivePacket();
			}
		}
		
		public function handlerRecivePacket():void{
			
			var receiveHandlerEvent:PacketHandler;
			if(recvPacket.getHeaderFlag() == 0){
				if(recvPacket.getCommandCode() == "PNG"){
					receiveHandlerEvent = new PacketHandler("receivePingPacket",recvPacket.getCommandSenderID());
					dispatchEvent(receiveHandlerEvent);
				}else{
					receiveHandlerEvent = new PacketHandler("receiveNormalActivePacket",recvPacket.getCommandSenderID());
					if(recvPacket.commandSenderID in this.invokeMap){
						var context:Context = this.invokeMap[recvPacket.commandSenderID];
						context.receivedPacket = recvPacket;
						context.receiveCallback(context.sentPacket,context.receivedPacket,context.args);
						removeCallback(recvPacket.commandSenderID);
					}
					dispatchEvent(receiveHandlerEvent);
				}
			}else if(recvPacket.getHeaderFlag() == 1){
				receiveHandlerEvent = new PacketHandler("receiveNormalPassivePacket");
				dispatchEvent(receiveHandlerEvent);
			}else if(recvPacket.getHeaderFlag() == 17){
				receiveHandlerEvent = new PacketHandler("receiveEncryptPassivePacket");
				dispatchEvent(receiveHandlerEvent);
			}
		}
		
		public function sendPacket(p:Packet,recieveCallback:Function = null, args:Parameters = null, timeout:int = 30):void{
			if(recieveCallback != null){
				var context:Context = new Context(p,recieveCallback,args);
				invokeMap[p.commandSenderID] = context;
			}
			p.printPacket();
			var sendByteArray:ByteArray = new ByteArray();
			sendByteArray.endian = Endian.LITTLE_ENDIAN;
			sendByteArray = p.toByteArray();
			sock.writeBytes(sendByteArray,0,sendByteArray.length);
			//trace("Da gui goi tin");
			sock.flush();
			myTimer.reset();
			myTimer.start();
		}
		
		public function closeConnection(event:Event):void{
			sock.close();
			trace("Close connection");
		}
		
		public function ioErrorConnection(event:IOErrorEvent):void{
			trace("Error IOConnection");
		}
	}
	
}
